// test_app_multithread.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <time.h>

#define _GNU_SOURCE
#include <fcntl.h> // For posix_fadvise

#define FILE_NAME "test_file.dat"
//#define FILE_SIZE (256 * 1024 * 1024 ) // 256 MB
#define FILE_SIZE (256LL * 1024 * 1024 * 64) // 16GB
//#define FILE_SIZE (256LL * 1024 * 1024 * 512) // 128GB
#define PAGE_SIZE 4096

#define NUM_THREADS 4
#define STRIDE (16 * PAGE_SIZE) // 64 KB stride for each thread

// Arguments for each thread
typedef struct {
    int thread_id;
    char *map_ptr;
    size_t start_offset;
    size_t end_offset;
} thread_args_t;

// Function to create a large file
void create_test_file() {
    int fd = open(FILE_NAME, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open for create");
        exit(1);
    }
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        exit(1);
    }
    close(fd);
    // V2.2: Fix format specifier warning
    printf("Created test file '%s' of size %zu MB\n", FILE_NAME, (size_t)(FILE_SIZE / (1024 * 1024)));
}

// Thread's main work function
void* thread_worker(void* args) {
    thread_args_t* t_args = (thread_args_t*)args;
    volatile char sink;

    printf("Thread %d starting. Accessing range [0x%zx, 0x%zx) with stride %d KB\n",
           t_args->thread_id, t_args->start_offset, t_args->end_offset, STRIDE / 1024);

    for (size_t i = t_args->start_offset; i < t_args->end_offset; i += STRIDE) {
        sink = t_args->map_ptr[i];
    }
    (void)sink; // V2.2: Suppress "unused variable" warning

    printf("Thread %d finished.\n", t_args->thread_id);
    return NULL;
}

int main(int argc, char *argv[]) {
    create_test_file();

    int fd = open(FILE_NAME, O_RDONLY);
    if (fd == -1) {
        perror("open for mmap");
        exit(1);
    }

    // Hint to kernel to disable its default readahead
    if (posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM) != 0) {
        perror("posix_fadvise(RANDOM) failed");
    } else {
        printf("Hinted to kernel to disable default readahead (POSIX_FADV_RANDOM).\n");
    }

    char *map = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(1);
    }

    printf("My PID is: %d. Run the prefetcher with this PID.\n", getpid());
    printf("Press Enter to start the multi-threaded test...");
    getchar();

    printf("Dropping file caches...\n");
    sync();
    int drop_fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
    if (drop_fd != -1) {
        if (write(drop_fd, "1", 1) == -1) {
           perror("write to drop_caches");
        }
        close(drop_fd);
    } else {
        perror("open /proc/sys/vm/drop_caches");
        printf("Warning: Could not drop file caches. Please run as root.\n");
    }
    sleep(1);

    pthread_t threads[NUM_THREADS];
    thread_args_t thread_args[NUM_THREADS];
    size_t chunk_size = FILE_SIZE / NUM_THREADS;

    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);

    for (int i = 0; i < NUM_THREADS; i++) {
        thread_args[i].thread_id = i;
        thread_args[i].map_ptr = map;
        thread_args[i].start_offset = i * chunk_size;
        thread_args[i].end_offset = (i + 1) * chunk_size;
        if (pthread_create(&threads[i], NULL, thread_worker, &thread_args[i]) != 0) {
            perror("pthread_create");
            exit(1);
        }
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        if (pthread_join(threads[i], NULL) != 0) {
            perror("pthread_join");
            exit(1);
        }
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    printf("All threads finished. Total execution time: %.4f seconds.\n", elapsed);

    munmap(map, FILE_SIZE);
    close(fd);
    return 0;
}